goog.provide('dk.xml');

goog.require('dk.util');

goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.string');


/**
 * @param {{title:string,
 *          domHelper:!goog.dom.DomHelper,
 *          id:string,
 *          byline:?{firstName:string,surname:string},
 *          isAppendix:?boolean}} options
 * @return {?string}
 */
dk.xml.generateDocBookXml = function(options) {
  var domHelper = options.domHelper;

  var xml = [];

  var isAppendix = !!options.isAppendix;
  var sectionType = isAppendix ? 'appendix' : 'chapter';

  var title = options.title;
  var byline = options.byline ?
    '<chapterinfo>\n' +
    '  <author>\n' +
    '    <firstname>' + goog.string.htmlEscape(options.byline.firstName) + '</firstname>\n' +
    '    <surname>' + goog.string.htmlEscape(options.byline.surname) + '</surname>\n' +
    '  </author>\n' +
    '</chapterinfo>\n'
    : '';
  var docId = goog.string.htmlEscape(options.id).replace(/\W/g, '_');
  xml.push(
    '<?xml version="1.0"?>\n',
    '<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" \n',
    ' "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">\n',
    '<' + sectionType + ' id="' + docId + '">\n',
    byline,
    '<title>', title, '</title>\n');

  var isInBigSection = false;
  var isInSection = false;
  var isInSmallSection = false;
  var isInItemizedList = false;

  /**
   * @type {function(Element, number=):string}
   */
  var elToXml = function(el, sectionNumber) {
    var text = goog.dom.getRawTextContent(el);
    text = goog.string.trim(text);
    if (sectionNumber) {
      var id;
      var match = text.match(/^(.*) id:([\w-]+)$/);
      if (match) {
        text = match[1];
        id = match[2];
      }
      
      var openTag = id ? '<sect' + sectionNumber + ' id="' + id + '">' :
          '<sect' + sectionNumber + '>';
      return openTag + '\n<title>' + goog.string.htmlEscape(text) + '</title>\n';
    } else {
      return goog.string.htmlEscape(text);
    }
  };
  
  var linkRe = /\[\[([^\[]*)\]\]/g;
  var italicRe = /::(.?[^:])*::/g;
  var tipPrefix = 'TIP:';
  var cautionPrefix = 'CAUTION:';

  var getFileContent = function(uri) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', uri, false /* async */);
    xhr.send(null);
    return xhr.responseText;
  }

  var paragraphElToXml = function(el, size, prefix) {
    prefix = prefix || '';
    
    var xml = elToXml(el);

    var outerTag;
    if (goog.string.startsWith(xml, tipPrefix)) {
      xml = xml.substring(tipPrefix.length);
      outerTag = 'tip';
    } else if (goog.string.startsWith(xml, cautionPrefix)) {
      xml = xml.substring(cautionPrefix.length);
      outerTag = 'caution';
    }

    // Replace [[http://www.google.com]] with <ulink url="http://www.google.com"/>
    // Replace [[xref:closure-service-ui]] with <xref linkend="closure-service-ui"/>
    xml = dk.util.formatXmlLink(xml);
    
    // Replace ::new term:: with <emphasis>new term</emphasis>
    xml = dk.util.formatEmphasis(xml);

    xml = dk.util.formatBackquote(xml);

    xml = '<para>\n' + dk.xml.breakLines(xml, size, prefix) + '\n' + prefix +
        '</para>\n';
    if (outerTag) {
      xml = prefix + '<' + outerTag + '>\n' + xml + prefix + '</' + outerTag + '>\n';
    }
    
    return xml;
  };

  var el = domHelper.getDocument().body.firstElementChild;
  if (!goog.dom.classes.has(el, 'big-heading')) {
    xml.push('<sect1>\n<title></title>\n');
    isInBigSection = true;
  }

  dk.util.forEachElementChild(domHelper, function(el) {
    var isListItem = dk.util.isListItem(el);
    if (isInItemizedList && !isListItem) {
      xml.push('</itemizedlist>\n');
      isInItemizedList = false;
    }

    if (dk.util.isParagraph(el)) {
      xml.push(paragraphElToXml(el));
    } else if (dk.util.isSourceCode(el)) {
      var textContent = goog.dom.getRawTextContent(el);      
      if (goog.string.startsWith(textContent, 'HTMLVERBATIM')) {
        // Ignore.
      } else if (goog.string.startsWith(textContent, 'XMLVERBATIM')) {
        xml.push(textContent.substring('XMLVERBATIM'.length) + '\n');
      } else {
        xml.push('<programlisting>',
                 elToXml(el),
                 '</programlisting>\n');
      }
    } else if (goog.dom.classes.has(el, 'include')) {
      // TODO(bolinfest): Support this important feature.
//      var path = goog.dom.getRawTextContent(el);
//      var uri = 'file://' + DIR + path;
//      xml.push(
//          '<!-- include ' + path + ' -->\n' + 
//          '<programlisting>',
//          goog.string.htmlEscape(getFileContent(uri)),
//          '</programlisting>\n');
    } else if (dk.util.isH1(el)) {
      if (isInSmallSection) {
        xml.push('</sect3>\n\n');
        isInSmallSection = false;
      }
      if (isInSection) {
        xml.push('</sect2>\n');
        isInSection = false;
      }
      if (isInBigSection) {
        xml.push('</sect1>\n\n');
      }
      xml.push(elToXml(el, 1));
      isInBigSection = true;
    } else if (dk.util.isH2(el)) {
      if (isInSmallSection) {
        xml.push('</sect3>\n\n');
        isInSmallSection = false;
      }
      if (isInSection) {
        xml.push('</sect2>\n\n');
      }
      xml.push(elToXml(el, 2));
      isInSection = true;
    } else if (dk.util.isH3(el)) {
      if (isInSmallSection) {
        xml.push('</sect3>\n\n');
      }
      xml.push(elToXml(el, 3));
      isInSmallSection = true;
    } else if (isListItem) {
      if (!isInItemizedList) {
        xml.push('<itemizedlist>\n');
        isInItemizedList = true;
      }
      // Indent 2 spaces.
      var indent = '  ';
      xml.push(indent, '<listitem>\n  ',
          paragraphElToXml(el, 80 - indent.length, indent), indent, '</listitem>\n');
    } else if (el.nodeName == 'BR' && !el.nextElementSibling) {
      // Ignore a <br> if it is the last element.

      // Google Docs seems to like to add this in.

    } else {
      throw new Error('No matching CSS class: ' + el.nodeName + ': ' + el.innerHTML +
          ' This may be caused by your Google Docs settings. ' +
          ' See the FAQ for details.');
    }
  });

  if (isInItemizedList) {
    xml.push('</itemizedlist>\n');
    isInItemizedList = false;
  }
  if (isInSmallSection) {
    xml.push('</sect3>\n');
    isInSmallSection = false;
  }
  if (isInSection) {
    xml.push('</sect2>\n');
    isInSection = false;
  }
  if (isInBigSection) {
    xml.push('</sect1>\n');
    isInBigSection = false;
  }

  xml.push('</' + sectionType + '>\n');

  return xml.join('');
};

/**
 * @param {string} str String to break into lines.
 * @param {number} size Defaults to 80.
 * @param {string=} prefix Defaults to ''.
 * @return {string}
 */
dk.xml.breakLines = function(str, size, prefix) {
  if (!goog.isDef(size)) {
    size = 80;
  }
  var chunks = [];
  var lastBreakIndex = 0;
  var lastCandidateIndex = 0;
  var i = 0;
  while (true) {
    var newIndex = str.indexOf(' ', lastCandidateIndex + 1);
    if (newIndex < 0) {
      if (lastBreakIndex != 0) {
        ++lastBreakIndex;
      }
      chunks.push(str.substring(lastBreakIndex, lastCandidateIndex));
      if (lastCandidateIndex != 0) {
        ++lastCandidateIndex;
      }
      chunks.push(str.substring(lastCandidateIndex));
      break;
    } else if (newIndex - lastBreakIndex <= size) {
      lastCandidateIndex = newIndex;
    } else {
      if (lastBreakIndex == lastCandidateIndex) {
        // This happens when there is an unbreakable string that is longer than
        // the size argument.
        lastCandidateIndex = newIndex;
      }
      if (lastBreakIndex != 0) {
        ++lastBreakIndex;
      }
      chunks.push(str.substring(lastBreakIndex, lastCandidateIndex));
      lastBreakIndex = lastCandidateIndex;
    } 
  }
  if (prefix) {
    goog.array.forEach(chunks, function(item, i) {
      chunks[i] = prefix + item;
    });
  }
  return chunks.join('\n');
};
